# author ： 以凝
# time   ： 2024/3/17  22:10
import codecs
import csv
import os
import platform
import shutil
from functools import partial
import webbrowser as wb
from libs.custom.dat_process import *
from libs.resources_rc import *
from .window_skeleton import *
from ..canvas import *
from ..colorDialog import *
from ..constants import *
from ..create_ml_io import *
from ..hashableQListWidgetItem import HashableQListWidgetItem
from ..image_format import *
from ..labelDialog import *
from ..labelFile import *
from ..lightWidget import *
from ..list_item import *
from ..pascal_voc_io import *
from ..play.play_thread import PlayThread
from ..settings import *
from ..shape import *
from ..stringBundle import *
from ..utils import *
from ..yolo_io import *
from ..zoomWidget import *
from ..custom.dat_process import DataFile


os.path.normpath("C:/Users/<NAME>/Desktop")

class MainWindow(WindowMixin):
    FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = list(range(3))

    def __init__(self, default_filename=None, default_prefdef_class_file=None):
        super(MainWindow, self).__init__()
        self.setWindowTitle(APP_NAME)


        self.os_name = platform.system()  # Me:Windows or mac

        # Load string bundle for i18n
        self.string_bundle = StringBundle.get_bundle()  # 加载汉化插件
        get_str = lambda str_id: self.string_bundle.get_string(str_id)


        # Yang TODO : 软件的元数据原本是不保存save_dir的   我认为保存更好
        self.default_save_dir = self.settings.get(SETTING_SAVE_DIR, DEFAULT_SAVE_DIR)  # 如果setting有存储，则导入;否则取默认值
        self.label_file_format = self.settings.get(SETTING_LABEL_FILE_FORMAT,
                                              LabelFileFormat.PASCAL_VOC)  # LabelFileFormat.PASCAL_VOC is default
        self.dat_save_format = self.settings.get(SETTING_DAT_SAVE_FORMAT, ImageFormat.PNG)

        # For loading all image under a directory
        self.modal1_img_list = []
        self.modal2_img_list = []

        self.dir_name = None
        self.label_hist = []
        self.last_open_dir = None
        self.cur_img_idx = 0
        self.modal1_img_count = len(self.modal1_img_list)
        self.modal2_img_count = len(self.modal2_img_list)

        # Whether we need to save or not.
        self.dirty = False  # 脏标记
        self.dat_opened = False

        self._no_selection_slot = False
        self._beginner = True

        # Load predefined classes to the list
        self.load_predefined_classes(default_prefdef_class_file)  # 从predefined_classes加载类别

        if self.label_hist:
            self.default_label = self.label_hist[0]  # 如果defaul_label不是None，那么取第0个类别作为默认类别
        else:
            print("Not find:/data/predefined_classes.txt (optional)")

        # Main widgets and related state.
        self.label_dialog = LabelDialog(parent=self, list_item=self.label_hist)

        self.items_to_shapes = {}
        self.shapes_to_items = {}
        self.prev_label_text = ''



        settings = self.settings




        # 点击文件树系统的条目实现打开图片
        self.tree1.doubleClicked.connect(partial(self.file_item_double_clicked , modal=1))
        self.tree2.doubleClicked.connect(partial(self.file_item_double_clicked , modal=2))


        self.has_setup_play_thread = False
        '''
            zoom ： 缩放
            light : 调亮调暗
            color ： 调色板
        '''
        self.zoom_widget = ZoomWidget()
        self.light_widget = LightWidget(get_str('lightWidgetTitle'))  # 图像亮度
        self.color_dialog = ColorDialog(parent=self)

        self.modal1 = None
        self.modal2 = None

        scroll_modal1 = QScrollArea()
        scroll_modal2 = QScrollArea()
        scroll_modal1.setWidget(self.canvas_modal1)
        scroll_modal2.setWidget(self.canvas_modal2)
        scroll_modal1.setWidgetResizable(True)
        self.scroll_bars = {  # 应该是图片的横纵轴
            Qt.Vertical: scroll_modal1.verticalScrollBar(),
            Qt.Horizontal: scroll_modal1.horizontalScrollBar()
        }
        self.scroll_area = scroll_modal1

        # canvas 事件连接槽
        self.canvas_modal1.scrollRequest.connect(self.scroll_request)
        self.canvas_modal1.newShape.connect(self.new_shape)
        self.canvas_modal1.shapeMoved.connect(self.set_dirty)
        self.canvas_modal1.selectionChanged.connect(self.shape_selection_changed)
        self.canvas_modal1.drawingPolygon.connect(self.toggle_drawing_sensitive)
        self.canvas_modal1.zoomRequest.connect(self.zoom_request)
        self.canvas_modal1.lightRequest.connect(self.light_request)

        self.canvas_modal2.scrollRequest.connect(self.scroll_request)
        self.canvas_modal2.newShape.connect(self.new_shape)
        self.canvas_modal2.shapeMoved.connect(self.set_dirty)
        self.canvas_modal2.selectionChanged.connect(self.shape_selection_changed)
        self.canvas_modal2.drawingPolygon.connect(self.toggle_drawing_sensitive)
        self.canvas_modal2.zoomRequest.connect(self.zoom_request)
        self.canvas_modal2.lightRequest.connect(self.light_request)

        # 创建QSplitter对象，在水平方向分割这两个scroll areas
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(scroll_modal1)
        splitter.addWidget(scroll_modal2)

        self.setCentralWidget(splitter)

        self.dock_features = QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetFloatable
        self.property_dock.setFeatures(self.property_dock.features() ^ self.dock_features)

        # Actions
        action = partial(new_action, self)
        quit = action(get_str('quit'), self.close,
                      'Ctrl+Q', 'quit', get_str('quitApp'))

        open = action(get_str('openFile'), self.open_file,
                      'Ctrl+O', 'open', get_str('openFileDetail'))

        open_modal1_dir = action(get_str('openDirOfModal1'), self.open_dir_dialog,
                          'Ctrl+u', 'open', get_str('openDir'))

        open_modal2_dir = action(get_str('openDirOfModal2'), partial(self.open_dir_dialog , modal = 2),
                                 'Ctrl+m+,', 'open', get_str('openDir'))


        change_save_dir = action(get_str('changeSaveDir'), self.change_save_dir_dialog,
                                 'Ctrl+r', 'open', get_str('changeSavedAnnotationDir'))

        open_annotation = action(get_str('openAnnotation'), self.open_annotation_dialog,
                                 'Ctrl+Shift+O', 'open', get_str('openAnnotationDetail'))

        play_frame = action(get_str('play'), self.play_frame , 'Ctrl+P', 'play_frame', get_str('playDetail'))

        pause_frame = action(get_str('pause'), self.pause_frame , 'Ctrl+Shift+P', 'pause_frame', get_str('pauseDetail'))

        copy_prev_bounding = action(get_str('copyPrevBounding'), self.copy_previous_bounding_boxes, 'Ctrl+v', 'copy',
                                    get_str('copyPrevBounding'))

        open_next_image = action(get_str('nextImg'), self.open_next_image,
                                 'd', 'next', get_str('nextImgDetail'))

        open_prev_image = action(get_str('prevImg'), self.open_prev_image,
                                 'a', 'prev', get_str('prevImgDetail'))

        verify = action(get_str('verifyImg'), self.verify_image,
                        'space', 'verify', get_str('verifyImgDetail'))

        save = action(get_str('save'), self.save_file,
                      'Ctrl+S', 'save', get_str('saveDetail'), enabled=False)

        saveImage = action(get_str('saveImage'), self.save_dat_image_file,
                           'Ctrl+S+I', 'save', get_str('saveImageDetail'), enabled=True)
        saveCsv = action(get_str('saveCsv'), self.save_csv_file,
                         'Ctrl+S+C', 'save', get_str('saveCsvDetail'), enabled=True)

        def get_format_meta(format):
            """
            returns a tuple containing (title, icon_name) of the selected format
            """
            if format == LabelFileFormat.PASCAL_VOC:
                return '&PascalVOC', 'format_voc'
            elif format == LabelFileFormat.YOLO:
                return '&YOLO', 'format_yolo'
            elif format == LabelFileFormat.CREATE_ML:
                return '&CreateML', 'format_createml'


        save_format = action(get_format_meta(self.label_file_format)[0],
                             self.change_format, 'Ctrl+Y',
                             get_format_meta(self.label_file_format)[1],
                             get_str('changeSaveFormat'), enabled=True)


        save_as = action(get_str('saveAs'), self.save_file_as,
                         'Ctrl+Shift+S', 'save-as', get_str('saveAsDetail'), enabled=False)

        close = action(get_str('closeCur'), self.close_file, 'Ctrl+W', 'close', get_str('closeCurDetail'))


        delete_image = action(get_str('deleteImg'), self.delete_image, 'Ctrl+Shift+D', 'close',
                              get_str('deleteImgDetail'))

        reset_all = action(get_str('resetAll'), self.reset_all, None, 'resetall', get_str('resetAllDetail'))

        color1 = action(get_str('boxLineColor'), self.choose_color1,
                        'Ctrl+L', 'color_line', get_str('boxLineColorDetail'))

        create_mode = action(get_str('crtBox'), self.set_create_mode,
                             'w', 'new', get_str('crtBoxDetail'), enabled=False)
        edit_mode = action(get_str('editBox'), self.set_edit_mode,
                           'Ctrl+J', 'edit', get_str('editBoxDetail'), enabled=False)

        create = action(get_str('crtBox'), self.create_shape,
                        'w', 'new', get_str('crtBoxDetail'), enabled=False)
        delete = action(get_str('delBox'), self.delete_selected_shape,
                        'Delete', 'delete', get_str('delBoxDetail'), enabled=False)
        copy = action(get_str('dupBox'), self.copy_selected_shape,
                      'Ctrl+D', 'copy', get_str('dupBoxDetail'),
                      enabled=False)

        advanced_mode = action(get_str('advancedMode'), self.toggle_advanced_mode,
                               'Ctrl+Shift+A', 'expert', get_str('advancedModeDetail'),
                               checkable=True)

        hide_all = action(get_str('hideAllBox'), partial(self.toggle_polygons, False),
                          'Ctrl+H', 'hide', get_str('hideAllBoxDetail'),
                          enabled=False)
        show_all = action(get_str('showAllBox'), partial(self.toggle_polygons, True),
                          'Ctrl+A', 'hide', get_str('showAllBoxDetail'),
                          enabled=False)

        help_default = action(get_str('tutorialDefault'), self.show_default_tutorial_dialog, None, 'help',
                              get_str('tutorialDetail'))
        show_info = action(get_str('info'), self.show_info_dialog, None, 'help', get_str('info'))
        show_shortcut = action(get_str('shortcut'), self.show_shortcuts_dialog, None, 'help', get_str('shortcut'))

        zoom = QWidgetAction(self)
        zoom.setDefaultWidget(self.zoom_widget)
        self.zoom_widget.setWhatsThis(
            u"Zoom in or out of the image. Also accessible with"
            " %s and %s from the canvas." % (format_shortcut("Ctrl+[-+]"),
                                             format_shortcut("Ctrl+Wheel")))
        self.zoom_widget.setEnabled(False)

        zoom_in = action(get_str('zoomin'), partial(self.add_zoom, 10),
                         'Ctrl++', 'zoom-in', get_str('zoominDetail'), enabled=False)
        zoom_out = action(get_str('zoomout'), partial(self.add_zoom, -10),
                          'Ctrl+-', 'zoom-out', get_str('zoomoutDetail'), enabled=False)
        zoom_org = action(get_str('originalsize'), partial(self.set_zoom, 100),
                          'Ctrl+=', 'zoom', get_str('originalsizeDetail'), enabled=False)
        fit_window = action(get_str('fitWin'), self.set_fit_window,
                            'Ctrl+F', 'fit-window', get_str('fitWinDetail'),
                            checkable=True, enabled=False)
        fit_width = action(get_str('fitWidth'), self.set_fit_width,
                           'Ctrl+Shift+F', 'fit-width', get_str('fitWidthDetail'),
                           checkable=True, enabled=False)
        # Group zoom controls into a list for easier toggling.
        zoom_actions = (self.zoom_widget, zoom_in, zoom_out,
                        zoom_org, fit_window, fit_width)
        self.zoom_mode = self.MANUAL_ZOOM
        self.scalers = {
            self.FIT_WINDOW: self.scale_fit_window,
            self.FIT_WIDTH: self.scale_fit_width,
            # Set to one to scale to 100% when loading files.
            self.MANUAL_ZOOM: lambda *args: 1,
        }

        light = QWidgetAction(self)
        light.setDefaultWidget(self.light_widget)
        self.light_widget.setWhatsThis(
            u"Brighten or darken current image. Also accessible with"
            " %s and %s from the canvas." % (format_shortcut("Ctrl+Shift+[-+]"),
                                             format_shortcut("Ctrl+Shift+Wheel")))
        self.light_widget.setEnabled(False)

        light_brighten = action(get_str('lightbrighten'), partial(self.add_light, 10),
                                'Ctrl+Shift++', 'light_lighten', get_str('lightbrightenDetail'), enabled=False)
        light_darken = action(get_str('lightdarken'), partial(self.add_light, -10),
                              'Ctrl+Shift+-', 'light_darken', get_str('lightdarkenDetail'), enabled=False)
        light_org = action(get_str('lightreset'), partial(self.set_light, 50),
                           'Ctrl+Shift+=', 'light_reset', get_str('lightresetDetail'), checkable=True, enabled=False)
        light_org.setChecked(True)

        # Group light controls into a list for easier toggling.
        light_actions = (self.light_widget, light_brighten,
                         light_darken, light_org)

        edit = action(get_str('editLabel'), self.edit_label,
                      'Ctrl+E', 'edit', get_str('editLabelDetail'),
                      enabled=False)
        # self.edit_button.setDefaultAction(edit)

        shape_line_color = action(get_str('shapeLineColor'), self.choose_shape_line_color,
                                  icon='color_line', tip=get_str('shapeLineColorDetail'),
                                  enabled=False)
        shape_fill_color = action(get_str('shapeFillColor'), self.choose_shape_fill_color,
                                  icon='color', tip=get_str('shapeFillColorDetail'),
                                  enabled=False)

        show_or_hide_property = self.property_dock.toggleViewAction()
        show_or_hide_property.setText(get_str('showHide'))
        show_or_hide_property.setShortcut('Ctrl+Shift+L')

        # Label list context menu.
        label_menu = QMenu()
        add_actions(label_menu, (edit, delete))
        self.property_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.property_list.customContextMenuRequested.connect(
            self.pop_label_list_menu)

        # Draw squares/rectangles
        self.draw_squares_option = QAction(get_str('drawSquares'), self)
        self.draw_squares_option.setShortcut('Ctrl+Shift+R')
        self.draw_squares_option.setCheckable(True)
        self.draw_squares_option.setChecked(self.settings.get(SETTING_DRAW_SQUARE, False))
        self.draw_squares_option.triggered.connect(self.toggle_draw_square)

        # Store actions for further handling.
        self.actions = Struct(save=save, saveImage=saveImage, saveCsv=saveCsv, save_format=save_format,
                            saveAs=save_as, open=open,
                              close=close, resetAll=reset_all, deleteImg=delete_image, play_frame=play_frame, pause_frame = pause_frame ,
                              lineColor=color1, create=create, delete=delete, edit=edit, copy=copy,
                              createMode=create_mode, editMode=edit_mode, advancedMode=advanced_mode,
                              shapeLineColor=shape_line_color, shapeFillColor=shape_fill_color,
                              zoom=zoom, zoomIn=zoom_in, zoomOut=zoom_out, zoomOrg=zoom_org,
                              fitWindow=fit_window, fitWidth=fit_width,
                              zoomActions=zoom_actions,
                              lightBrighten=light_brighten, lightDarken=light_darken, lightOrg=light_org,
                              lightActions=light_actions,
                              fileMenuActions=(
                                  open, open_modal1_dir, open_modal2_dir ,  save, saveImage, saveCsv, save_as, close,
                                  reset_all, quit),
                              beginner=(), advanced=(),
                              editMenu=(edit, copy, delete,
                                        None, color1, self.draw_squares_option),
                              beginnerContext=(create, edit, copy, delete),
                              advancedContext=(create_mode, edit_mode, edit, copy,
                                               delete, shape_line_color, shape_fill_color),
                              onLoadActive=(
                                  close, create, create_mode, edit_mode),
                              onShapesPresent=(save_as, hide_all, show_all))

        ''' 
            菜单栏  基本不用动
        '''
        self.menus = Struct(
            file=self.menu(get_str('menu_file')),
            edit=self.menu(get_str('menu_edit')),
            view=self.menu(get_str('menu_view')),
            help=self.menu(get_str('menu_help')),
            recentFiles=QMenu(get_str('menu_openRecent')),
            labelList=label_menu
        )

        # Auto saving : Enable auto saving if pressing next
        self.auto_saving = QAction(get_str('autoSaveMode'), self)
        self.auto_saving.setCheckable(True)
        self.auto_saving.setChecked(self.settings.get(SETTING_AUTO_SAVE, False))
        # Sync single class mode from PR#106
        self.single_class_mode = QAction(get_str('singleClsMode'), self)
        self.single_class_mode.setShortcut("Ctrl+Shift+S")
        self.single_class_mode.setCheckable(True)
        self.single_class_mode.setChecked(self.settings.get(SETTING_SINGLE_CLASS, False))
        self.lastLabel = None
        # Add option to enable/disable labels being displayed at the top of bounding boxes
        self.display_label_option = QAction(get_str('displayLabel'), self)
        self.display_label_option.setShortcut("Ctrl+Shift+P")
        self.display_label_option.setCheckable(True)
        self.display_label_option.setChecked(self.settings.get(SETTING_PAINT_LABEL, False))
        self.display_label_option.triggered.connect(self.toggle_paint_labels_option)

        # 向菜单栏->文件按钮 添加一系列动作
        add_actions(self.menus.file,
    (open, open_modal1_dir, open_modal2_dir , change_save_dir, open_annotation, copy_prev_bounding,
             self.menus.recentFiles, save, saveImage, saveCsv, save_format, save_as, close,
             reset_all, delete_image, quit)
        )

        add_actions(self.menus.help, (help_default, show_info, show_shortcut))
        add_actions(self.menus.view, (
            self.auto_saving,
            self.single_class_mode,
            self.display_label_option,
            show_or_hide_property, advanced_mode, None,
            hide_all, show_all, None,
            zoom_in, zoom_out, zoom_org, None,
            fit_window, fit_width, None,
            light_brighten, light_darken, light_org))

        self.menus.file.aboutToShow.connect(self.update_file_menu)

        # Custom context menu for the canvas widget:
        add_actions(self.canvas_modal1.menus[0], self.actions.beginnerContext)
        add_actions(self.canvas_modal1.menus[1], (
            action('&Copy here', self.copy_shape),
            action('&Move here', self.move_shape)))

        add_actions(self.canvas_modal2.menus[0], self.actions.beginnerContext)
        add_actions(self.canvas_modal2.menus[1], (
            action('&Copy here', self.copy_shape),
            action('&Move here', self.move_shape)))

        # 向工具栏添加具体功能
        self.tools = self.toolbar('Tools')
        self.actions.beginner = (
            open, open_modal1_dir, open_modal2_dir , change_save_dir, open_next_image, open_prev_image, verify, save, play_frame , pause_frame ,
            saveImage, saveCsv, save_format, None, create, copy, delete, None,
            zoom_in, zoom, zoom_out, fit_window, fit_width, None,
            light_brighten, light, light_darken, light_org)

        self.actions.advanced = (
            open, open_modal1_dir, open_modal2_dir , change_save_dir, open_next_image, open_prev_image, save, saveImage,play_frame, pause_frame ,
            saveCsv, save_format, None,
            create_mode, edit_mode, None,
            hide_all, show_all)

        self.statusBar().showMessage('%s started.' % APP_NAME)
        self.statusBar().show()

        # Application state.
        self.image = QImage()
        self.file_path = (default_filename)  # 图像存储路径
        self.last_open_dir = None
        self.recent_files = []
        self.max_recent = 7
        self.line_color = None
        self.fill_color = None
        self.zoom_level = 100
        self.fit_window = False

        # Fix the compatible issue for qt4 and qt5. Convert the QStringList to python list
        if self.settings.get(SETTING_RECENT_FILES):
            recent_file_qstring_list = self.settings.get(SETTING_RECENT_FILES)
            self.recent_files = [(i) for i in recent_file_qstring_list]

        size = settings.get(SETTING_WIN_SIZE, QSize(600, 500))
        position = QPoint(0, 0)
        saved_position = settings.get(SETTING_WIN_POSE, position)
        # Fix the multiple monitors issue
        for i in range(QApplication.desktop().screenCount()):
            if QApplication.desktop().availableGeometry(i).contains(saved_position):
                position = saved_position
                break
        self.resize(size)
        self.move(position)
        # save_dir = (settings.get(SETTING_SAVE_DIR, None))  # 默认的save_dir路径和图片的存储路径是一致的
        save_dir = (settings.get(SETTING_SAVE_DIR, DEFAULT_SAVE_DIR))  # 默认的save_dir路径和图片的存储路径是一致的
        self.last_open_dir = (settings.get(SETTING_LAST_OPEN_DIR, None))
        if self.default_save_dir is None and save_dir is not None and os.path.exists(save_dir):
            self.default_save_dir = save_dir
            self.statusBar().showMessage('%s started. Annotation will be saved to %s' %
                                         (APP_NAME, self.default_save_dir))
            self.statusBar().show()

        self.restoreState(settings.get(SETTING_WIN_STATE, QByteArray()))
        Shape.line_color = self.line_color = QColor(settings.get(SETTING_LINE_COLOR, DEFAULT_LINE_COLOR))
        Shape.fill_color = self.fill_color = QColor(settings.get(SETTING_FILL_COLOR, DEFAULT_FILL_COLOR))
        self.canvas_modal1.set_drawing_color(self.line_color)

        self.canvas_modal2.set_drawing_color(self.line_color)

        self.append_status_message(Message.make_message("Fuck Me" , True))
        self.append_status_message(Message.make_message("Fuck Me Again" , False))
        self.append_status_message(Message.make_message("You did a Good Job", True))

        def xbool(x):
            if isinstance(x, QVariant):
                return x.toBool()
            return bool(x)

        if xbool(settings.get(SETTING_ADVANCE_MODE, False)):
            self.actions.advancedMode.setChecked(True)
            self.toggle_advanced_mode()

        # Populate the File menu dynamically.
        self.update_file_menu()

        # Since loading the file may take some time, make sure it runs in the background.
        if self.file_path and os.path.isdir(self.file_path):
            self.queue_event(partial(self.import_dir_images, self.file_path or ""))
        elif self.file_path:
            self.queue_event(partial(self.load_file, self.file_path or ""))

        # Callbacks:
        self.zoom_widget.valueChanged.connect(self.paint_canvas)
        self.light_widget.valueChanged.connect(self.paint_canvas)

        self.populate_mode_actions()

        # Display cursor coordinates at the right of status bar
        self.label_coordinates = QLabel('')
        self.statusBar().addPermanentWidget(self.label_coordinates)

        # Open Dir if default file
        if self.file_path and os.path.isdir(self.file_path):
            self.open_dir_dialog(dir_path=self.file_path, silent=True)

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Control:
            self.canvas_modal1.set_drawing_shape_to_square(False)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Control:
            # Draw rectangle if Ctrl is pressed
            self.canvas_modal1.set_drawing_shape_to_square(True)

    # Support Functions #
    def set_format(self, save_format):
        '''
            左侧功能列表栏的存储格式的选择
        '''
        if save_format == FORMAT_PASCALVOC:
            self.actions.save_format.setText(FORMAT_PASCALVOC)
            self.actions.save_format.setIcon(new_icon("format_voc"))
            self.label_file_format = LabelFileFormat.PASCAL_VOC
            LabelFile.suffix = XML_EXT

        elif save_format == FORMAT_YOLO:
            self.actions.save_format.setText(FORMAT_YOLO)
            self.actions.save_format.setIcon(new_icon("format_yolo"))
            self.label_file_format = LabelFileFormat.YOLO
            LabelFile.suffix = TXT_EXT

        elif save_format == FORMAT_CREATEML:
            self.actions.save_format.setText(FORMAT_CREATEML)
            self.actions.save_format.setIcon(new_icon("format_createml"))
            self.label_file_format = LabelFileFormat.CREATE_ML
            LabelFile.suffix = JSON_EXT

    def set_image_save_format(self, save_format):
        '''
            左侧功能列表栏的存储格式的选择
        '''
        if save_format == DAT_SAVE_FORMAT_PNG:
            self.actions.save_dat_format.setText(DAT_SAVE_FORMAT_PNG)
            self.actions.save_dat_format.setIcon(new_icon("format_png"))
            self.dat_save_format = ImageFormat.PNG
            ImageFile.suffix = ".png"

        elif save_format == DAT_SAVE_FORMAT_JPG:
            self.actions.save_dat_format.setText(DAT_SAVE_FORMAT_JPG)
            self.actions.save_dat_format.setIcon(new_icon("format_jpg"))
            self.dat_save_format = ImageFormat.JPG
            ImageFile.suffix = ".jpg"

        elif save_format == DAT_SAVE_FORMAT_JPEG:
            self.actions.save_dat_format.setText(DAT_SAVE_FORMAT_JPEG)
            self.actions.save_dat_format.setIcon(new_icon("format_jpeg"))
            self.dat_save_format = ImageFormat.JPEG
            ImageFile.suffix = ".jpeg"

    def change_format(self):
        '''
            标注格式的修改
        '''
        if self.label_file_format == LabelFileFormat.PASCAL_VOC:
            self.set_format(FORMAT_YOLO)
        elif self.label_file_format == LabelFileFormat.YOLO:
            self.set_format(FORMAT_CREATEML)
        elif self.label_file_format == LabelFileFormat.CREATE_ML:
            self.set_format(FORMAT_PASCALVOC)
        else:
            raise ValueError('Unknown label file format.')
        self.set_dirty()

    def change_dat_format(self):
        '''
        标注格式的修改
        '''
        print("change_dat_format")
        if self.dat_save_format == ImageFormat.PNG:
            self.set_image_save_format(DAT_SAVE_FORMAT_JPG)
        elif self.dat_save_format == ImageFormat.JPG:
            self.set_image_save_format(DAT_SAVE_FORMAT_JPEG)
        elif self.dat_save_format == ImageFormat.JPEG:
            self.set_image_save_format(DAT_SAVE_FORMAT_PNG)
        else:
            raise ValueError('Unknown image file format.')
        # self.set_dirty()

    def no_shapes(self):
        return not self.items_to_shapes

    def toggle_advanced_mode(self, value=True):
        '''
            控制标注者模式和专家模式的转化
            一般不需要改动，与我无关
        '''
        self._beginner = not value
        self.canvas_modal1.set_editing(True)
        self.populate_mode_actions()
        # self.edit_button.setVisible(not value)
        if value:
            self.actions.createMode.setEnabled(True)
            self.actions.editMode.setEnabled(False)
            self.property_dock.setFeatures(self.property_dock.features() | self.dock_features)
        else:
            self.property_dock.setFeatures(self.property_dock.features() ^ self.dock_features)

    def populate_mode_actions(self):
        if self.beginner():
            tool, menu = self.actions.beginner, self.actions.beginnerContext
        else:
            tool, menu = self.actions.advanced, self.actions.advancedContext
        self.tools.clear()
        add_actions(self.tools, tool)
        self.canvas_modal1.menus[0].clear()
        add_actions(self.canvas_modal1.menus[0], menu)
        self.menus.edit.clear()
        actions = (self.actions.create,) if self.beginner() \
            else (self.actions.createMode, self.actions.editMode)
        add_actions(self.menus.edit, actions + self.actions.editMenu)

    def set_beginner(self):
        '''
            标注者模式
        '''
        self.tools.clear()
        add_actions(self.tools, self.actions.beginner)

    def set_advanced(self):
        '''
            专家模式
        '''
        self.tools.clear()
        add_actions(self.tools, self.actions.advanced)

    def set_dirty(self):
        self.dirty = True
        self.actions.save.setEnabled(True)

    def set_clean(self):
        self.dirty = False
        self.actions.save.setEnabled(False)
        self.actions.create.setEnabled(True)

    def toggle_actions(self, value=True):
        """Enable/Disable widgets which depend on an opened image."""
        for z in self.actions.zoomActions:
            z.setEnabled(value)
        for z in self.actions.lightActions:
            z.setEnabled(value)
        for action in self.actions.onLoadActive:
            action.setEnabled(value)

    def queue_event(self, function):
        QTimer.singleShot(0, function)

    def status(self, message, delay=5000):
        '''
            状态展示
            TODO 可能会需要添加某些中间步骤
        '''
        self.statusBar().showMessage(message, delay)

    def reset_state(self):
        '''
            元信息删除，全部重置
            与我无关 不需改动
        '''
        self.items_to_shapes.clear()
        self.shapes_to_items.clear()
        self.property_list.clear()
        self.file_path = None
        self.image_data = None
        self.label_file = None
        self.label_coordinates.clear()

    def current_item(self):
        '''
            展示已标注出来的目标
            界面右侧中间的功能
        '''
        items = self.label_list.selectedItems()  # 一般来讲items是一个列表，这个列表只存了一个目标 items[0]
        if items:
            return items[0]
        return None

    def add_recent_file(self, file_path):
        '''
            添加最近打开的文件
            与我无关
        '''
        if file_path in self.recent_files:
            self.recent_files.remove(file_path)
        elif len(self.recent_files) >= self.max_recent:
            self.recent_files.pop()
        self.recent_files.insert(0, file_path)

    def beginner(self):
        '''
            beginner  标注者模式
        '''
        return self._beginner

    def advanced(self):
        '''
            将 beginner 禁掉即为 专家模式
        '''
        return not self.beginner()

    def show_tutorial_dialog(self, browser='default', link=None):
        '''
            目测没有用
        '''
        return

    def show_default_tutorial_dialog(self):
        self.show_tutorial_dialog(browser='default')

    def show_info_dialog(self):
        from libs.__init__ import __version__
        msg = u'Name:{0} \nApp Version:{1} \n{2} '.format(APP_NAME, __version__, sys.version_info)
        QMessageBox.information(self, u'Information', msg)

    def show_shortcuts_dialog(self):
        self.show_tutorial_dialog(browser='default', link='https://github.com/tzutalin/labelImg#Hotkeys')

    def create_shape(self):
        assert self.beginner()
        self.canvas_modal1.set_editing(False)
        self.actions.create.setEnabled(False)

    def toggle_drawing_sensitive(self, drawing=True):
        """In the middle of drawing, toggling between modes should be disabled."""
        # 与我无关
        self.actions.editMode.setEnabled(not drawing)
        if not drawing and self.beginner():
            # Cancel creation.
            print('Cancel creation.')
            self.canvas_modal1.set_editing(True)
            self.canvas_modal1.restore_cursor()
            self.actions.create.setEnabled(True)

    def toggle_draw_mode(self, edit=True):
        '''
            toggle的作用是负责切换绘图模式和编辑模式的
            TODO 调控绘图模式的时候，应该得注意是绘制私有目标还是公有目标
        '''

        self.canvas_modal1.set_editing(edit)
        self.actions.createMode.setEnabled(edit)
        self.actions.editMode.setEnabled(not edit)

    def set_create_mode(self):
        '''
            创建区块 模式
            TODO : 创建区块需要区分创建的是 私有区块和公有区块
        '''
        assert self.advanced()
        self.toggle_draw_mode(False)

    def set_edit_mode(self):
        '''
            设置 编辑区块 mode
            与我无关
        '''
        assert self.advanced()  # 专家模式不可以进行 编辑区块
        self.toggle_draw_mode(True)
        self.label_selection_changed()

    def update_file_menu(self):
        '''
            打开界面就会执行
            截止目前只发现执行了一次
        '''
        curr_file_path = self.file_path  # self.file_path 就是命令行指定的打开的图片的dir路径，如果没有指定就是None

        def exists(filename):
            return os.path.exists(filename)

        menu = self.menus.recentFiles
        menu.clear()
        files = [f for f in self.recent_files if f !=
                 curr_file_path and exists(f)]
        for i, f in enumerate(files):
            icon = new_icon('labels')
            action = QAction(
                icon, '&%d %s' % (i + 1, QFileInfo(f).fileName()), self)
            action.triggered.connect(partial(self.load_recent, f))
            menu.addAction(action)
            # print(action)

    def pop_label_list_menu(self, point):
        self.menus.labelList.exec_(self.label_list.mapToGlobal(point))

    def edit_label(self):
        if not self.canvas_modal1.editing():
            return
        item = self.current_item()
        if not item:
            return
        text = self.label_dialog.pop_up(item.text())
        if text is not None:
            item.setText(text)
            item.setBackground(generate_color_by_text(text))
            self.set_dirty()
            self.update_combo_box()

    # Tzutalin 20160906 : Add file list and dock to move faster
    def file_item_double_clicked(self, item=None , modal=1):
        '''
            图片名称被双击之后会加载被双击的图片
            不需要改动
        '''
        img_list = self.modal1_img_list if modal == 1  else self.modal2_img_list
        self.cur_img_idx = img_list.index(item.model().filePath(item))
        if modal ==1 :
            self.load_file(item.model().filePath(item))
        else :
            self.load_file_modal2(item.model().filePath(item))



    def button_state(self, item=None):
        """ Function to handle difficult examples
        Update on each object """
        if not self.canvas_modal1.editing():
            return

        item = self.current_item()
        if not item:  # If not selected Item, take the first one
            item = self.label_list.item(self.label_list.count() - 1)

        difficult = self.diffc_button.isChecked()

        try:
            shape = self.items_to_shapes[item]
        except:
            pass
        # Checked and Update
        try:
            if difficult != shape.difficult:
                shape.difficult = difficult
                self.set_dirty()
            else:  # User probably changed item visibility
                self.canvas_modal1.set_shape_visible(shape, item.checkState() == Qt.Checked)
        except:
            pass

    # React to canvas signals.
    def shape_selection_changed(self, selected=False):
        if self._no_selection_slot:
            self._no_selection_slot = False
        else:
            shape = self.canvas_modal1.selected_shape
            if shape:
                self.shapes_to_items[shape].setSelected(True)
            else:
                self.label_list.clearSelection()
        self.actions.delete.setEnabled(selected)
        self.actions.copy.setEnabled(selected)
        self.actions.edit.setEnabled(selected)
        self.actions.shapeLineColor.setEnabled(selected)
        self.actions.shapeFillColor.setEnabled(selected)

    def add_label(self, shape):
        shape.paint_label = self.display_label_option.isChecked()
        item = HashableQListWidgetItem(shape.label)
        item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
        item.setCheckState(Qt.Checked)
        item.setBackground(generate_color_by_text(shape.label))
        self.items_to_shapes[item] = shape
        self.shapes_to_items[shape] = item
        self.label_list.addItem(item)
        for action in self.actions.onShapesPresent:
            action.setEnabled(True)
        self.update_combo_box()

    def remove_label(self, shape):
        if shape is None:
            # print('rm empty label')
            return
        item = self.shapes_to_items[shape]
        self.label_list.takeItem(self.label_list.row(item))
        del self.shapes_to_items[shape]
        del self.items_to_shapes[item]
        self.update_combo_box()

    def load_labels(self, shapes):
        s = []
        for label, points, line_color, fill_color, difficult in shapes:
            shape = Shape(label=label)
            for x, y in points:

                # Ensure the labels are within the bounds of the image. If not, fix them.
                x, y, snapped = self.canvas_modal1.snap_point_to_canvas(x, y)
                if snapped:
                    self.set_dirty()

                shape.add_point(QPointF(x, y))
            shape.difficult = difficult
            shape.close()
            s.append(shape)

            if line_color:
                shape.line_color = QColor(*line_color)
            else:
                shape.line_color = generate_color_by_text(label)

            if fill_color:
                shape.fill_color = QColor(*fill_color)
            else:
                shape.fill_color = generate_color_by_text(label)

            self.add_label(shape)
        self.update_combo_box()
        self.canvas_modal1.load_shapes(s)

    def update_combo_box(self):
        # Get the unique labels and add them to the Combobox.
        items_text_list = [str(self.label_list.item(i).text()) for i in range(self.label_list.count())]

        unique_text_list = list(set(items_text_list))
        # Add a null row for showing all the labels
        unique_text_list.append("")
        unique_text_list.sort()

        self.combo_box.update_items(unique_text_list)

    def save_labels(self, annotation_file_path):
        '''
            TODO :区分两种存储路径
        '''
        annotation_file_path = (annotation_file_path)
        if self.label_file is None:
            self.label_file = LabelFile()
            self.label_file.verified = self.canvas_modal1.verified

        def format_shape(s):
            return dict(label=s.label,
                        line_color=s.line_color.getRgb(),
                        fill_color=s.fill_color.getRgb(),
                        points=[(p.x(), p.y()) for p in s.points],
                        # add chris
                        difficult=s.difficult)

        shapes = [format_shape(shape) for shape in self.canvas_modal1.shapes]
        # Can add different annotation formats here
        try:
            if self.label_file_format == LabelFileFormat.PASCAL_VOC:
                if annotation_file_path[-4:].lower() != ".xml":
                    annotation_file_path += XML_EXT
                self.label_file.save_pascal_voc_format(annotation_file_path, shapes, self.file_path, self.image_data,
                                                       self.line_color.getRgb(), self.fill_color.getRgb())
            elif self.label_file_format == LabelFileFormat.YOLO:
                if annotation_file_path[-4:].lower() != ".txt":
                    annotation_file_path += TXT_EXT
                self.label_file.save_yolo_format(annotation_file_path, shapes, self.file_path, self.image_data,
                                                 self.label_hist,
                                                 self.line_color.getRgb(), self.fill_color.getRgb())
            elif self.label_file_format == LabelFileFormat.CREATE_ML:
                if annotation_file_path[-5:].lower() != ".json":
                    annotation_file_path += JSON_EXT
                self.label_file.save_create_ml_format(annotation_file_path, shapes, self.file_path, self.image_data,
                                                      self.label_hist, self.line_color.getRgb(),
                                                      self.fill_color.getRgb())
            else:
                self.label_file.save(annotation_file_path, shapes, self.file_path, self.image_data,
                                     self.line_color.getRgb(), self.fill_color.getRgb())
            print('Image:{0} -> Annotation:{1}'.format(self.file_path, annotation_file_path))
            return True
        except LabelFileError as e:
            self.error_message(u'Error saving label data', u'<b>%s</b>' % e)
            return False

    def copy_selected_shape(self):
        '''
            拷贝选中形状，一般用不太上
            不需要改动
        '''
        self.add_label(self.canvas_modal1.copy_selected_shape())
        # fix copy and delete
        self.shape_selection_changed(True)

    def combo_selection_changed(self, index):
        text = self.combo_box.cb.itemText(index)
        for i in range(self.label_list.count()):
            if text == "":
                self.label_list.item(i).setCheckState(2)
            elif text != self.label_list.item(i).text():
                self.label_list.item(i).setCheckState(0)
            else:
                self.label_list.item(i).setCheckState(2)

    def default_label_combo_selection_changed(self, index):
        '''
            修改预设标签
            不需要改动
        '''
        self.default_label = self.label_hist[index]

    def label_selection_changed(self):
        item = self.current_item()
        if item and self.canvas_modal1.editing():
            self._no_selection_slot = True
            self.canvas_modal1.select_shape(self.items_to_shapes[item])
            shape = self.items_to_shapes[item]

    def label_item_changed(self, item):
        shape = self.items_to_shapes[item]
        label = item.text()
        if label != shape.label:
            shape.label = item.text()
            shape.line_color = generate_color_by_text(shape.label)
            self.set_dirty()
        else:  # User probably changed item visibility
            self.canvas_modal1.set_shape_visible(shape, item.checkState() == Qt.Checked)

    # Callback functions:
    def new_shape(self):
        """Pop-up and give focus to the label editor.

        position MUST be in global coordinates.
        """
        if not self.use_default_label_checkbox.isChecked():
            if len(self.label_hist) > 0:
                self.label_dialog = LabelDialog(
                    parent=self, list_item=self.label_hist)

            # Sync single class mode from PR#106
            if self.single_class_mode.isChecked() and self.lastLabel:
                text = self.lastLabel
            else:
                text = self.label_dialog.pop_up(text=self.prev_label_text)
                self.lastLabel = text
        else:
            text = self.default_label

        if text is not None:
            self.prev_label_text = text
            generate_color = generate_color_by_text(text)
            shape = self.canvas_modal1.set_last_label(text, generate_color, generate_color)
            self.add_label(shape)
            if self.beginner():  # Switch to edit mode.
                self.canvas_modal1.set_editing(True)
                self.actions.create.setEnabled(True)
            else:
                self.actions.editMode.setEnabled(True)
            self.set_dirty()

            if text not in self.label_hist:
                self.label_hist.append(text)
        else:
            # self.canvas_modal1.undoLastLine()
            self.canvas_modal1.reset_all_lines()

    def scroll_request(self, delta, orientation):
        units = - delta / (8 * 15)
        bar = self.scroll_bars[orientation]
        bar.setValue(int(bar.value() + bar.singleStep() * units))

    def set_zoom(self, value):
        self.actions.fitWidth.setChecked(False)
        self.actions.fitWindow.setChecked(False)
        self.zoom_mode = self.MANUAL_ZOOM
        # Arithmetic on scaling factor often results in float
        # Convert to int to avoid type errors
        self.zoom_widget.setValue(int(value))

    def add_zoom(self, increment=10):
        self.set_zoom(self.zoom_widget.value() + increment)

    def zoom_request(self, delta):
        # get the current scrollbar positions
        # calculate the percentages ~ coordinates
        h_bar = self.scroll_bars[Qt.Horizontal]
        v_bar = self.scroll_bars[Qt.Vertical]

        # get the current maximum, to know the difference after zooming
        h_bar_max = h_bar.maximum()
        v_bar_max = v_bar.maximum()

        # get the cursor position and canvas size
        # calculate the desired movement from 0 to 1
        # where 0 = move left
        #       1 = move right
        # up and down analogous
        cursor = QCursor()
        pos = cursor.pos()
        relative_pos = QWidget.mapFromGlobal(self, pos)

        cursor_x = relative_pos.x()
        cursor_y = relative_pos.y()

        w = self.scroll_area.width()
        h = self.scroll_area.height()

        # the scaling from 0 to 1 has some padding
        # you don't have to hit the very leftmost pixel for a maximum-left movement
        margin = 0.1
        move_x = (cursor_x - margin * w) / (w - 2 * margin * w)
        move_y = (cursor_y - margin * h) / (h - 2 * margin * h)

        # clamp the values from 0 to 1
        move_x = min(max(move_x, 0), 1)
        move_y = min(max(move_y, 0), 1)

        # zoom in
        units = delta // (8 * 15)
        scale = 10
        self.add_zoom(scale * units)

        # get the difference in scrollbar values
        # this is how far we can move
        d_h_bar_max = h_bar.maximum() - h_bar_max
        d_v_bar_max = v_bar.maximum() - v_bar_max

        # get the new scrollbar values
        new_h_bar_value = int(h_bar.value() + move_x * d_h_bar_max)
        new_v_bar_value = int(v_bar.value() + move_y * d_v_bar_max)

        h_bar.setValue(new_h_bar_value)
        v_bar.setValue(new_v_bar_value)

    def light_request(self, delta):
        self.add_light(5 * delta // (8 * 15))

    def set_fit_window(self, value=True):
        if value:
            self.actions.fitWidth.setChecked(False)
        self.zoom_mode = self.FIT_WINDOW if value else self.MANUAL_ZOOM
        self.adjust_scale(modal=1)
        self.adjust_scale(modal=2)

    def set_fit_width(self, value=True):
        if value:
            self.actions.fitWindow.setChecked(False)
        self.zoom_mode = self.FIT_WIDTH if value else self.MANUAL_ZOOM
        self.adjust_scale(modal=1)
        self.adjust_scale(modal=2)

    def set_light(self, value):
        self.actions.lightOrg.setChecked(int(value) == 50)
        # Arithmetic on scaling factor often results in float
        # Convert to int to avoid type errors
        self.light_widget.setValue(int(value))

    def add_light(self, increment=10):
        '''
        给图片提升亮度
        与我无关
        '''
        self.set_light(self.light_widget.value() + increment)

    def toggle_polygons(self, value):
        for item, shape in self.items_to_shapes.items():
            item.setCheckState(Qt.Checked if value else Qt.Unchecked)

    def load_file(self, file_path=None):
        """
            Load the specified file, or the last opened file if None.
            加载特定的文件
        """

        painted = self.canvas_modal1
        painted.reset_state()
        self.reset_state()
        painted.setEnabled(False)
        if file_path is None:
            file_path = self.settings.get(SETTING_FILENAME)
        # Make sure that filePath is a regular python string, rather than QString
        file_path = (file_path)

        # Fix bug: An  index error after select a directory when open a new file.
        unicode_file_path = (file_path)
        unicode_file_path = os.path.abspath(unicode_file_path)
        # Tzutalin 20160906 : Add file list and dock to move faster

        # 如果需要记得高亮被选中的条目

        if unicode_file_path and os.path.exists(unicode_file_path):
            if LabelFile.is_label_file(unicode_file_path):
                try:
                    self.label_file = LabelFile(unicode_file_path)
                except LabelFileError as e:
                    self.error_message(u'Error opening file',
                                       (u"<p><b>%s</b></p>"
                                        u"<p>Make sure <i>%s</i> is a valid label file.")
                                       % (e, unicode_file_path))
                    self.status("Error reading %s" % unicode_file_path)

                    return False
                self.image_data = self.label_file.image_data
                # self.line_color = QColor(*self.label_file.lineColor)
                # self.fill_color = QColor(*self.label_file.fillColor)
                painted.verified = self.label_file.verified
            elif DataFile.is_data_file(unicode_file_path) :
                dat_data = DataFile.read_dat(unicode_file_path, None)
                img = dat_data["img"]
                info = dat_data["info"]
                # 显示固有属性
                if info is not None :
                    for k, v in info.items():
                        info = Format_KV(k, v)
                        item = QListWidgetItem(info)
                        self.property_list.addItem(item)
                # 图片绘制
                image = None
                if img is not None:
                    print("dat : self.img is not None")
                    width, height = 512, 512
                    self.image_data = QImage(img, width, height, QImage.Format_Grayscale8)

            else: # default picture
                # Load image:
                # read data first and store for saving into label file.
                self.image_data = read(unicode_file_path, None)
                self.label_file = None
                painted.verified = False  ################################



            if isinstance(self.image_data, QImage):
                image = self.image_data
            else:
                image = QImage.fromData(self.image_data)
            if image.isNull():
                self.error_message(u'Error opening file',
                                   u"<p>Make sure <i>%s</i> is a valid image file." % unicode_file_path)
                self.status("Error reading %s" % unicode_file_path)
                return False
            self.status("Loaded %s" % os.path.basename(unicode_file_path))
            self.image = image
            self.file_path = unicode_file_path
            painted.load_pixmap(QPixmap.fromImage(image))
            if self.label_file:
                self.load_labels(self.label_file.shapes)
            self.set_clean()
            painted.setEnabled(True)
            self.adjust_scale(initial=True)
            self.paint_canvas(modal=1)
            self.add_recent_file(self.file_path)
            self.toggle_actions(True)
            self.show_bounding_box_from_annotation_file(self.file_path)

            counter = self.counter_str(modal=1)
            self.setWindowTitle(APP_NAME + ' ' + file_path + ' ' + counter)

            # Default : select last item if there is at least one item
            # if self.property_list.count():
            #     self.property_list.setCurrentItem(self.label_list.item(self.label_list.count() - 1))
            #     self.property_list.item(self.label_list.count() - 1).setSelected(True)

            painted.setFocus(True)
            return True
        return False

    def load_file_modal2(self, file_path=None):
        """
            Load the specified file, or the last opened file if None.
            加载特定的文件
            通过file_path判断当前应该往哪个canvas上绘制
        """
        self.reset_state()
        self.canvas_modal2.setEnabled(False)
        if file_path is None:
            file_path = self.settings.get(SETTING_FILENAME)
        # Make sure that filePath is a regular python string, rather than QString
        file_path = (file_path)

        # Fix bug: An  index error after select a directory when open a new file.
        unicode_file_path = (file_path)
        unicode_file_path = os.path.abspath(unicode_file_path)
        # Tzutalin 20160906 : Add file list and dock to move faster


        if unicode_file_path and os.path.exists(unicode_file_path):
            if LabelFile.is_label_file(unicode_file_path):
                try:
                    self.label_file = LabelFile(unicode_file_path)
                except LabelFileError as e:
                    self.error_message(u'Error opening file',
                                       (u"<p><b>%s</b></p>"
                                        u"<p>Make sure <i>%s</i> is a valid label file.")
                                       % (e, unicode_file_path))
                    self.status("Error reading %s" % unicode_file_path)

                    return False
                self.image_data = self.label_file.image_data
                # self.line_color = QColor(*self.label_file.lineColor)
                # self.fill_color = QColor(*self.label_file.fillColor)
                self.canvas_modal2.verified = self.label_file.verified
            elif DataFile.is_data_file(unicode_file_path) :
                dat_data = DataFile.read_dat(unicode_file_path, None)
                img = dat_data["img"]
                info = dat_data["info"]
                # 显示固有属性
                if info is not None :
                    for k, v in info.items():
                        info = Format_KV(k, v)
                        item = QListWidgetItem(info)
                        self.property_list.addItem(item)
                # 图片绘制
                image = None
                if img is not None:
                    print("dat : self.img is not None")
                    width, height = 512, 512
                    self.image_data = QImage(img, width, height, QImage.Format_Grayscale8)

            else: # default picture
                # Load image:
                # read data first and store for saving into label file.
                self.image_data = read(unicode_file_path, None)
                self.label_file = None
                self.canvas_modal2.verified = False  ################################



            if isinstance(self.image_data, QImage):
                image = self.image_data
            else:
                image = QImage.fromData(self.image_data)
            if image.isNull():
                self.error_message(u'Error opening file',
                                   u"<p>Make sure <i>%s</i> is a valid image file." % unicode_file_path)
                self.status("Error reading %s" % unicode_file_path)
                return False
            self.status("Loaded %s" % os.path.basename(unicode_file_path))
            self.image = image
            self.file_path = unicode_file_path
            self.canvas_modal2.load_pixmap(QPixmap.fromImage(image))
            if self.label_file:
                self.load_labels(self.label_file.shapes)
            self.set_clean()
            self.canvas_modal2.setEnabled(True)
            self.adjust_scale(initial=True,modal=2)
            self.paint_canvas(modal=2)
            self.add_recent_file(self.file_path)
            self.toggle_actions(True)
            self.show_bounding_box_from_annotation_file(self.file_path)

            counter = self.counter_str(modal=2)
            self.setWindowTitle(APP_NAME + ' ' + file_path + ' ' + counter)

            # Default : select last item if there is at least one item
            # if self.property_list.count():
            #     self.property_list.setCurrentItem(self.label_list.item(self.label_list.count() - 1))
            #     self.property_list.item(self.label_list.count() - 1).setSelected(True)

            self.canvas_modal2.setFocus(True)
            return True
        return False

    def counter_str(self , modal=1):
        """
        Converts image counter to string representation.
        位于框标题，图片路径后
        无需处理
        """
        return '[{} / {}]'.format(self.cur_img_idx + 1, self.modal1_img_count if modal == 1 else self.modal2_img_count)

    def show_bounding_box_from_annotation_file(self, file_path):
        '''
            从标注文件导入bbox
        '''
        if self.default_save_dir is not None:
            basename = os.path.basename(os.path.splitext(file_path)[0])
            xml_path = os.path.join(self.default_save_dir, basename + XML_EXT)
            txt_path = os.path.join(self.default_save_dir, basename + TXT_EXT)
            json_path = os.path.join(self.default_save_dir, basename + JSON_EXT)

            """
            Annotation file priority:
            PascalXML > YOLO
            """
            if os.path.isfile(xml_path):
                self.load_pascal_xml_by_filename(xml_path)
            elif os.path.isfile(txt_path):
                self.load_yolo_txt_by_filename(txt_path)
            elif os.path.isfile(json_path):
                self.load_create_ml_json_by_filename(json_path, file_path)

        else:
            xml_path = os.path.splitext(file_path)[0] + XML_EXT
            txt_path = os.path.splitext(file_path)[0] + TXT_EXT
            json_path = os.path.splitext(file_path)[0] + JSON_EXT

            if os.path.isfile(xml_path):
                self.load_pascal_xml_by_filename(xml_path)
            elif os.path.isfile(txt_path):
                self.load_yolo_txt_by_filename(txt_path)
            elif os.path.isfile(json_path):
                self.load_create_ml_json_by_filename(json_path, file_path)

    def resizeEvent(self, event):
        '''
            resize 事件， 应该和我的项目没有直接关系
            TODO 删掉注释
        '''
        if self.canvas_modal1 and not self.image.isNull() \
                and self.zoom_mode != self.MANUAL_ZOOM:
            self.adjust_scale()
        super(MainWindow, self).resizeEvent(event)

    def paint_canvas(self,modal=1):
        '''
            负责将图片显示在界面中央
        '''
        if modal == 1 :
            assert not self.image.isNull(), "cannot paint null image"
            self.canvas_modal1.scale = 0.01 * self.zoom_widget.value()
            self.canvas_modal1.overlay_color = self.light_widget.color()
            self.canvas_modal1.label_font_size = int(0.02 * max(self.image.width(), self.image.height()))
            self.canvas_modal1.adjustSize()
            self.canvas_modal1.update()
        elif modal == 2 :
            assert not self.image.isNull(), "cannot paint null image"
            self.canvas_modal2.scale = 0.01 * self.zoom_widget.value()
            self.canvas_modal2.overlay_color = self.light_widget.color()
            self.canvas_modal2.label_font_size = int(0.02 * max(self.image.width(), self.image.height()))
            self.canvas_modal2.adjustSize()
            self.canvas_modal2.update()

    def adjust_scale(self, initial=False , modal=1):
        value = partial(self.scalers[self.FIT_WINDOW if initial else self.zoom_mode],modal)()
        self.zoom_widget.setValue(int(100 * value))

    def scale_fit_window(self , modal):
        """Figure out the size of the pixmap in order to fit the main widget."""
        e = 2.0  # So that no scrollbars are generated.
        w1 = self.centralWidget().width() - e
        h1 = self.centralWidget().height() - e
        a1 = w1 / h1
        # Calculate a new scale value based on the pixmap's aspect ratio.
        if modal == 1 :
            w2 = self.canvas_modal1.pixmap.width() - 0.0
            h2 = self.canvas_modal1.pixmap.height() - 0.0
        else :
            w2 = self.canvas_modal2.pixmap.width() - 0.0
            h2 = self.canvas_modal2.pixmap.height() - 0.0

        a2 = w2 / h2
        return w1 / w2 if a2 >= a1 else h1 / h2

    def scale_fit_width(self , modal = 1 ):
        # The epsilon does not seem to work too well here.
        w = self.centralWidget().width() - 2.0
        if modal == 1 :
            return w / self.canvas_modal1.pixmap.width()
        else :
            return w / self.canvas_modal2.pixmap.width()
    def closeEvent(self, event):
        '''
            关闭事件
            关闭前对当前状态进行存储
            下一次打开的时候导入当前状态
        '''
        if not self.may_continue():
            event.ignore()
        settings = self.settings
        # If it loads images from dir, don't load it at the beginning
        if self.dir_name is None:
            settings[SETTING_FILENAME] = self.file_path if self.file_path else ''
        else:
            settings[SETTING_FILENAME] = ''

        settings[SETTING_WIN_SIZE] = self.size()
        settings[SETTING_WIN_POSE] = self.pos()
        settings[SETTING_WIN_STATE] = self.saveState()
        settings[SETTING_LINE_COLOR] = self.line_color
        settings[SETTING_FILL_COLOR] = self.fill_color
        settings[SETTING_RECENT_FILES] = self.recent_files
        settings[SETTING_ADVANCE_MODE] = not self._beginner
        if self.default_save_dir and os.path.exists(self.default_save_dir):
            settings[SETTING_SAVE_DIR] = (self.default_save_dir)
            print("self.default save dir", self.default_save_dir)
            print("saving setting", settings[SETTING_SAVE_DIR])
        else:
            settings[SETTING_SAVE_DIR] = ''

        if self.last_open_dir and os.path.exists(self.last_open_dir):
            settings[SETTING_LAST_OPEN_DIR] = self.last_open_dir
        else:
            settings[SETTING_LAST_OPEN_DIR] = ''

        settings[SETTING_AUTO_SAVE] = self.auto_saving.isChecked()
        settings[SETTING_SINGLE_CLASS] = self.single_class_mode.isChecked()
        settings[SETTING_PAINT_LABEL] = self.display_label_option.isChecked()
        settings[SETTING_DRAW_SQUARE] = self.draw_squares_option.isChecked()
        settings[SETTING_LABEL_FILE_FORMAT] = self.label_file_format

    def load_recent(self, filename):
        '''
            加载最近的文件
            不需要我改动
        '''
        if self.may_continue():
            self.load_file(filename)

    def scan_all_images(self, folder_path):
        '''
            扫描全部的图像，把folder_path文件夹中的图片(凡是PyQt支持的图片种类)路径全部放进一个list并返回
        '''
        extensions = ['.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()]
        extensions.extend(['.dat', '.data'])
        folder_path = Path(folder_path)
        images = []
        for path in folder_path.rglob('*'):
            if path.is_file() and path.suffix.lower() in extensions:
                images.append(path.as_posix())
        natural_sort(images, key=lambda x: x.lower())
        return images

    def change_save_dir_dialog(self, _value=False):
        '''
            修改save_dir对话框
            TODO 修改成两种save路径
        '''
        if self.default_save_dir is not None:
            path = (self.default_save_dir)
        else:
            path = '.'

        dir_path = (QFileDialog.getExistingDirectory(self,
                                                         '%s - Save annotations to the directory' % APP_NAME, path,
                                                         QFileDialog.ShowDirsOnly
                                                         | QFileDialog.DontResolveSymlinks))

        if dir_path is not None and len(dir_path) > 1:
            self.default_save_dir = dir_path

        self.show_bounding_box_from_annotation_file(self.file_path)

        self.statusBar().showMessage('%s . Annotation will be saved to %s' %
                                     ('Change saved folder', self.default_save_dir))
        self.statusBar().show()

    def open_annotation_dialog(self, _value=False):
        '''
            打开标注对话框  不需要过多注意
        '''
        if self.file_path is None:
            self.statusBar().showMessage('Please select image first')
            self.statusBar().show()
            return

        path = os.path.dirname((self.file_path)) \
            if self.file_path else '.'
        if self.label_file_format == LabelFileFormat.PASCAL_VOC:
            filters = "Open Annotation XML file (%s)" % ' '.join(['*.xml'])
            filename = (QFileDialog.getOpenFileName(self, '%s - Choose a xml file' % APP_NAME, path, filters))
            if filename:
                if isinstance(filename, (tuple, list)):
                    filename = filename[0]
            self.load_pascal_xml_by_filename(filename)

        elif self.label_file_format == LabelFileFormat.CREATE_ML:

            filters = "Open Annotation JSON file (%s)" % ' '.join(['*.json'])
            filename = (QFileDialog.getOpenFileName(self, '%s - Choose a json file' % APP_NAME, path, filters))
            if filename:
                if isinstance(filename, (tuple, list)):
                    filename = filename[0]

            self.load_create_ml_json_by_filename(filename, self.file_path)

    def open_dir_dialog(self, _value=False, dir_path=None, silent=False , modal = 1 ):
        if not self.may_continue():
            return
        if self.last_open_dir and os.path.exists(self.last_open_dir):
            default_open_dir_path = self.last_open_dir
        else:
            default_open_dir_path = os.path.dirname(self.file_path) if self.file_path else '.'
        if silent != True:
            target_dir_path = (QFileDialog.getExistingDirectory(self,
                                                                    '%s - Open Directory' % APP_NAME,
                                                                    default_open_dir_path,
                                                                    QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks))
        else:
            target_dir_path = (default_open_dir_path)

        self.set_file_explorer_root_path(target_dir_path, modal)  # 在这里模态属性将被设置
        self.last_open_dir = target_dir_path
        self.import_dir_images(target_dir_path , modal)  # 这时候会用到模态属性

        self.default_save_dir = target_dir_path
        if self.file_path:
            self.show_bounding_box_from_annotation_file(file_path=self.file_path)


    def import_dir_images(self, dir_path , modal):
        '''
            打开了图片文件夹，open_dir_dialog会调用该函数进行图像导入
        '''
        if not self.may_continue() or not dir_path:
            return

        self.last_open_dir = dir_path
        self.dir_name = dir_path
        self.file_path = None
        if modal == 1 :
            self.modal1_img_list = self.scan_all_images(dir_path)
            self.modal1_img_count = len(self.modal1_img_list)
            self.open_next_image(modal=modal)

        else :
            self.modal2_img_list = self.scan_all_images(dir_path)
            self.modal2_img_count = len(self.modal2_img_list)
            self.open_next_image(modal=modal)


    def verify_image(self, _value=False):
        '''
            验证模式（专家模式）的时候会发挥作用  我不需要过多关注
        '''
        # Proceeding next image without dialog if having any label
        if self.file_path is not None:
            try:
                self.label_file.toggle_verify()
            except AttributeError:
                # If the labelling file does not exist yet, create if and
                # re-save it with the verified attribute.
                self.save_file()
                if self.label_file is not None:
                    self.label_file.toggle_verify()
                else:
                    return

            self.canvas_modal1.verified = self.label_file.verified
            self.paint_canvas()
            self.save_file()

    def open_prev_image(self, _value=False , modal=1):
        '''
            Proceeding prev image without dialog if having any label
            点击“前一张图像”就会触发  不管能不能打开前一张图像
            不需要过多关注
        '''
        img_list = self.modal1_img_list if modal == 1 else self.modal2_img_list
        other_img_list = self.modal2_img_list if img_list == self.modal1_img_list else self.modal1_img_list
        img_count = len(img_list)
        load_file = self.load_file if modal == 1 else self.load_file_modal2
        other_load = self.load_file_modal2 if modal == 1 else self.load_file
        file_modal = self.modal1_file_modal if modal == 1 else self.modal2_file_modal
        other_file_modal = self.modal2_file_modal if file_modal == self.modal1_file_modal else self.modal1_file_modal
        tree = self.tree1 if modal == 1 else self.tree2
        other_tree = self.tree2 if tree == self.tree1 else self.tree1
        if self.auto_saving.isChecked():
            if self.default_save_dir is not None:
                if self.dirty is True:
                    self.save_file()
            else:
                self.change_save_dir_dialog()
                return

        if not self.may_continue():
            return


        if img_count <= 0:
            return



        if self.cur_img_idx - 1 >= 0:
            self.cur_img_idx -= 1

            # 这么写是为了支持单模态的时候的播放功能
            if img_list :
                filename = img_list[self.cur_img_idx]
                # 打开选中
                model_index = file_modal.index(filename)
                if model_index.isValid():
                    tree.setCurrentIndex(model_index)
                    tree.scrollTo(model_index)
                    tree.selectionModel().select(model_index, QItemSelectionModel.Select)
                    load_file(filename)
            if other_img_list :
                other_modal_filename = other_img_list[self.cur_img_idx]
                # 另一个模态同步
                other_model_index = other_file_modal.index(other_modal_filename)
                if other_model_index.isValid():
                    other_tree.setCurrentIndex(other_model_index)
                    other_tree.scrollTo(other_model_index)
                    other_tree.selectionModel().select(other_model_index, QItemSelectionModel.Select)
                    other_load(other_modal_filename)

    def open_next_image(self, _value=False , modal=1):
        '''
            Proceeding next image without dialog if having any label
        '''
        img_list = self.modal1_img_list if modal == 1 else self.modal2_img_list
        other_img_list = self.modal2_img_list if img_list == self.modal1_img_list else self.modal1_img_list
        img_count = len(img_list)
        load_file = self.load_file if modal == 1 else self.load_file_modal2
        other_load = self.load_file_modal2 if modal == 1 else self.load_file
        file_modal = self.modal1_file_modal if modal == 1 else self.modal2_file_modal
        other_file_modal = self.modal2_file_modal if file_modal == self.modal1_file_modal else self.modal1_file_modal
        tree = self.tree1 if modal == 1 else self.tree2
        other_tree = self.tree2 if tree == self.tree1 else self.tree1
        if self.auto_saving.isChecked():
            if self.default_save_dir is not None:
                if self.dirty is True:
                    self.save_file()
            else:
                self.change_save_dir_dialog()
                return

        if not self.may_continue():
            return

        if img_count <= 0:
            return

        if not img_list:
            return

        if self.has_setup_play_thread and self.cur_img_idx == img_count - 1 : # 到头了
            self.pause_frame()
            return

        # 最初打开图片文件夹，此时其实没有按下一张的按钮
        if self.file_path is None:
            filename = img_list[0]
            self.cur_img_idx = 0
            load_file(filename)
            return
        # 普通情况 即按下一张的按钮
        if self.cur_img_idx + 1 < img_count:
            self.cur_img_idx += 1
            if img_list :
                filename = img_list[self.cur_img_idx]
                # 打开选中
                model_index = file_modal.index(filename)
                if tree and model_index.isValid():
                    tree.setCurrentIndex(model_index)
                    tree.scrollTo(model_index)
                    tree.selectionModel().select(model_index, QItemSelectionModel.Select)
                    load_file(filename)


            if other_img_list :
                other_modal_filename = other_img_list[self.cur_img_idx]
                # 另一个模态同步
                other_model_index = other_file_modal.index(other_modal_filename)
                if other_tree and other_model_index.isValid():
                    other_tree.setCurrentIndex(other_model_index)
                    other_tree.scrollTo(other_model_index)
                    other_tree.selectionModel().select(other_model_index, QItemSelectionModel.Select)
                    other_load(other_modal_filename)


    def open_file(self, _value=False):
        '''
            打开单个文件，作用不是很大
            与项目的关系不大
        '''
        if not self.may_continue():
            return
        path = os.path.dirname((self.file_path)) if self.file_path else '.'
        formats = ['*.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()]
        formats.extend(['*.dat', '*.data'])
        filters = "Image & Label files (%s)" % ' '.join(formats + ['*%s' % LabelFile.suffix])
        filename, _ = QFileDialog.getOpenFileName(self, '%s - Choose Image or Label file' % APP_NAME, path, filters)
        if filename:
            if isinstance(filename, (tuple, list)):
                filename = filename[0]
            self.cur_img_idx = 0
            self.img_count = 1
            self.load_file(filename)

    def save_file(self, _value=False):
        '''
            点击save按钮 ， 保存功能会触发
        '''
        if self.default_save_dir is not None and len((self.default_save_dir)):
            if self.file_path:
                image_file_name = os.path.basename(self.file_path)
                saved_file_name = os.path.splitext(image_file_name)[0]
                saved_path = os.path.join((self.default_save_dir), saved_file_name)
                self._save_file(saved_path)
        else:
            image_file_dir = os.path.dirname(self.file_path)
            image_file_name = os.path.basename(self.file_path)
            saved_file_name = os.path.splitext(image_file_name)[0]
            saved_path = os.path.join(image_file_dir, saved_file_name)
            self._save_file(saved_path if self.label_file
                            else self.save_file_dialog(remove_ext=False))

    def save_dat_image_file(self, _value=False):
        '''
        点击save_csv按钮 ， 属性将会写到csv中
    ~   '''
        if self.dat_opened and self.img is not None:
            print(self.file_path)
            file_dir = os.path.dirname(self.file_path) + "\\..\\DAT\\"
            file_name = os.path.basename(self.file_path)
            saved_file_name = os.path.splitext(file_name)[0] + ImageFile.suffix
            save_path = os.path.join(file_dir, saved_file_name)
            self.img.save(save_path, ImageFile.suffix[1:])  # 去掉.

    def save_csv_file(self, _value=False):
        '''
            点击save_csv按钮 ， 属性将会写到csv中
        '''
        if self.dat_opened:
            print(self.file_path)
            file_dir = os.path.dirname(self.file_path) + "\\..\\csv\\"
            file_name = os.path.basename(self.file_path)
            saved_file_name = os.path.splitext(file_name)[0] + ".csv"
            save_path = os.path.join(file_dir, saved_file_name)
            array = np.array(list(self.dat_data.values())).T
            print(save_path)
            with open(save_path, "w", newline='') as f:
                writer = csv.writer(f)
                writer.writerow(self.dat_data.keys())
                writer.writerows(array)

    def save_file_as(self, _value=False):
        assert not self.image.isNull(), "cannot save empty image"
        self._save_file(self.save_file_dialog())

    def save_file_dialog(self, remove_ext=True):
        caption = '%s - Choose File' % APP_NAME
        filters = 'File (*%s)' % LabelFile.suffix
        open_dialog_path = self.current_path()
        dlg = QFileDialog(self, caption, open_dialog_path, filters)
        dlg.setDefaultSuffix(LabelFile.suffix[1:])
        dlg.setAcceptMode(QFileDialog.AcceptSave)
        filename_without_extension = os.path.splitext(self.file_path)[0]
        dlg.selectFile(filename_without_extension)
        dlg.setOption(QFileDialog.DontUseNativeDialog, False)
        if dlg.exec_():
            full_file_path = (dlg.selectedFiles()[0])
            if remove_ext:
                return os.path.splitext(full_file_path)[0]  # Return file path without the extension.
            else:
                return full_file_path
        return ''

    def _save_file(self, annotation_file_path):
        if annotation_file_path and self.save_labels(annotation_file_path):
            self.set_clean()
            self.statusBar().showMessage('Saved to  %s' % annotation_file_path)
            self.statusBar().show()

    def play_frame(self):
        if self.has_setup_play_thread :
            return
        self.has_setup_play_thread = True
        self.play_thread = PlayThread(interval=0.05)
        self.play_thread.update_request.connect(self.open_next_image)
        self.play_thread.error.connect(self.handle_error)
        self.play_thread.start()

    def pause_frame(self):
        # 停止播放线程
        self.has_setup_play_thread = False
        self.play_thread.stop()
        self.play_thread.wait()  # 等待线程结束

    def handle_error(self, error):
        if error is not None:
            self.append_status_message(Message.make_message(error , True))


    def close_file(self, _value=False):
        if not self.may_continue():
            return
        self.reset_state()
        self.set_clean()
        self.toggle_actions(False)
        self.canvas_modal1.setEnabled(False)
        self.actions.saveAs.setEnabled(False)

    def delete_image(self):
        delete_path = self.file_path
        if delete_path is not None:
            idx = self.cur_img_idx
            if os.path.exists(delete_path):
                os.remove(delete_path)
            self.import_dir_images(self.last_open_dir)
            if self.img_count > 0:
                self.cur_img_idx = min(idx, self.img_count - 1)
                filename = self.m_img_list[self.cur_img_idx]
                self.load_file(filename)
            else:
                self.close_file()

    def reset_all(self):
        self.settings.reset()
        self.close()
        process = QProcess()
        process.startDetached(os.path.abspath(__file__))

    def may_continue(self):
        if not self.dirty:  # 如果脏标记是False,那么继续执行
            return True
        else:
            discard_changes = self.discard_changes_dialog()
            if discard_changes == QMessageBox.No:
                return True
            elif discard_changes == QMessageBox.Yes:
                self.save_file()
                return True
            else:
                return False

    def discard_changes_dialog(self):
        '''
            丢弃修改
        '''
        yes, no, cancel = QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel
        msg = u'You have unsaved changes, would you like to save them and proceed?\nClick "No" to undo all changes.'
        return QMessageBox.warning(self, u'Attention', msg, yes | no | cancel)

    def error_message(self, title, message):
        return QMessageBox.critical(self, title,
                                    '<p><b>%s</b></p>%s' % (title, message))

    def current_path(self):
        '''
            另存为按钮会触发
        '''
        return os.path.dirname(self.file_path) if self.file_path else '.'

    def choose_color1(self):
        color = self.color_dialog.getColor(self.line_color, u'Choose line color',
                                           default=DEFAULT_LINE_COLOR)
        if color:
            self.line_color = color
            Shape.line_color = color
            self.canvas_modal1.set_drawing_color(color)
            self.canvas_modal1.update()
            self.set_dirty()

    def delete_selected_shape(self):
        self.remove_label(self.canvas_modal1.delete_selected())
        self.set_dirty()
        if self.no_shapes():
            for action in self.actions.onShapesPresent:
                action.setEnabled(False)

    def choose_shape_line_color(self):
        color = self.color_dialog.getColor(self.line_color, u'Choose Line Color',
                                           default=DEFAULT_LINE_COLOR)
        if color:
            self.canvas_modal1.selected_shape.line_color = color
            self.canvas_modal1.update()
            self.set_dirty()

    def choose_shape_fill_color(self):
        color = self.color_dialog.getColor(self.fill_color, u'Choose Fill Color',
                                           default=DEFAULT_FILL_COLOR)
        if color:
            self.canvas_modal1.selected_shape.fill_color = color
            self.canvas_modal1.update()
            self.set_dirty()

    def copy_shape(self):
        '''
            复制一样的标注
        '''
        if self.canvas_modal1.selected_shape is None:
            # True if one accidentally touches the left mouse button before releasing
            return
        self.canvas_modal1.end_move(copy=True)
        self.add_label(self.canvas_modal1.selected_shape)
        self.set_dirty()

    def move_shape(self):
        self.canvas_modal1.end_move(copy=False)
        self.set_dirty()

    def load_predefined_classes(self, predef_classes_file):
        '''
            加载预定义的类别，即加载predefined_classes.txt内的类别内容
        '''
        if os.path.exists(predef_classes_file) is True:
            with codecs.open(predef_classes_file, 'r', 'utf8') as f:
                for line in f:
                    line = line.strip()
                    if self.label_hist is None:
                        self.label_hist = [line]
                    else:
                        self.label_hist.append(line)

    def load_pascal_xml_by_filename(self, xml_path):
        '''
            加载xml标注   不用动
        '''
        if self.file_path is None:
            return
        if os.path.isfile(xml_path) is False:
            return

        self.set_format(FORMAT_PASCALVOC)

        t_voc_parse_reader = PascalVocReader(xml_path)
        shapes = t_voc_parse_reader.get_shapes()
        self.load_labels(shapes)
        self.canvas_modal1.verified = t_voc_parse_reader.verified

    def load_yolo_txt_by_filename(self, txt_path):
        '''
            加载yolo标注  不用动
        '''
        if self.file_path is None:
            return
        if os.path.isfile(txt_path) is False:
            return

        self.set_format(FORMAT_YOLO)
        t_yolo_parse_reader = YoloReader(txt_path, self.image)
        shapes = t_yolo_parse_reader.get_shapes()
        print(shapes)
        self.load_labels(shapes)
        self.canvas_modal1.verified = t_yolo_parse_reader.verified

    def load_create_ml_json_by_filename(self, json_path, file_path):
        '''
            加载json标注 不用动
        '''
        if self.file_path is None:
            return
        if os.path.isfile(json_path) is False:
            return

        self.set_format(FORMAT_CREATEML)

        create_ml_parse_reader = CreateMLReader(json_path, file_path)
        shapes = create_ml_parse_reader.get_shapes()
        self.load_labels(shapes)
        self.canvas_modal1.verified = create_ml_parse_reader.verified

    def copy_previous_bounding_boxes(self):
        current_index = self.m_img_list.index(self.file_path)
        if current_index - 1 >= 0:
            prev_file_path = self.m_img_list[current_index - 1]
            self.show_bounding_box_from_annotation_file(prev_file_path)
            self.save_file()

    def toggle_paint_labels_option(self):
        for shape in self.canvas_modal1.shapes:
            shape.paint_label = self.display_label_option.isChecked()

    def toggle_draw_square(self):
        self.canvas_modal1.set_drawing_shape_to_square(self.draw_squares_option.isChecked())
